package in.co.codedoc.sql;

import in.co.codedoc.cg.annotations.DBType;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;

public class WhereClause
{
	public static enum Op
	{
		EQUALS,
		NOT_EQUALS,
		IN,
		NOT_IN,
		IS_LESS_THAN,
		IS_LESS_THAN_OR_EQUAL_TO,
		IS_GREATER_THAN,
		IS_GREATER_THAN_OR_EQUAL_TO,
		IS_NULL,
		IS_NOT_NULL,
		IS_LIKE;
		
		public boolean IsUnary()
		{
			switch(this)
			{
			case IS_NULL:
			case IS_NOT_NULL:
				return true;
			default:
				return false;
			}
		}

		@Override
		public String toString()
		{
			switch(this)
			{
			case EQUALS:return "=";
			case NOT_EQUALS:return "!=";
			case IN:return "IN";
			case NOT_IN:return "NOT IN";
			case IS_LESS_THAN:return "<";
			case IS_LESS_THAN_OR_EQUAL_TO:return "<=";
			case IS_GREATER_THAN:return ">";
			case IS_GREATER_THAN_OR_EQUAL_TO:return ">=";
			case IS_LIKE:return "LIKE";
			case IS_NULL:return "IS NULL";
			case IS_NOT_NULL:return "IS NOT NULL";
			default:return "";
			}
		}

		public PrintWriter Generate(PrintWriter out) 
			throws IOException
		{
			out.write(toString());
			return out;
		}	
	}

	private static enum ConjunctiveOp
	{
		AND,
		OR;

		public PrintWriter Generate(PrintWriter out) 
			throws IOException
		{
			switch(this)
			{
			case AND:
				out.write("AND");
				break;
			case OR:
				out.write("OR");
				break;
			default:
				break;
			}
			
			return out;
		}
	}
	
	private static class WhereClauseElement
	{
		public WhereClauseElement(RelationalExpression relationalExpression)
		{
			this.relationalExpression = relationalExpression;
		}

		public WhereClauseElement(WhereClause whereClause)
		{
			this.whereClause = whereClause;
		}
	
		public boolean IsRelationalExpression()
		{
			return relationalExpression != null;
		}

		public boolean IsWhereClause()
		{
			return whereClause != null;
		}
		
		public RelationalExpression GetRelationalExpression()
		{
			return relationalExpression;
		}
		
		public WhereClause GetWhereClause()
		{
			return whereClause;
		}
		
		private RelationalExpression relationalExpression;
		private WhereClause whereClause;
	}
	
	public WhereClause()
	{
		this((SQLStatement)null);
	}
	
	public WhereClause(SQLStatement owner)
	{
		this.owner = owner;
		this.conjunctive = ConjunctiveOp.AND;
		this.elements = new LinkedList<WhereClauseElement>();
	}
	
	public WhereClause(WhereClause whereClause)
	{
		this.conjunctive = whereClause.conjunctive;
		this.elements = new LinkedList<WhereClauseElement>(whereClause.elements);
		this.owner = whereClause.owner;
	}

	public boolean IsEmpty()
	{
		return elements == null || elements.isEmpty();
	}

	public Set<Table> GetTables()
	{
		Set<Table> tables = new HashSet<Table>();
		for(WhereClauseElement wce:elements)
		{
			if(wce.IsWhereClause())
			{
				tables.addAll(wce.GetWhereClause().GetTables());
			}
			else
			{
				tables.addAll(wce.GetRelationalExpression().GetTables());				
			}
		}
		
		return tables;
	}
	
	public WhereClause And(WhereClause where)
	{
		return Add(ConjunctiveOp.AND, where);
	}

	public WhereClause And(TableColumn tc,SubqueryStatement subquery)
	{
		return And(tc,Op.EQUALS,subquery);
	}
	
	public WhereClause And(TableColumn tc1,TableColumn tc2)
	{
		return And(tc1,Op.EQUALS,tc2);
	}

	public WhereClause And(TableColumn tc,String v)
	{
		return And(tc,Op.EQUALS,v);
	}
	
	public WhereClause And(TableColumn tc,short v)
	{
		return And(tc,Op.EQUALS,v);
	}

	public WhereClause And(TableColumn tc,int v)
	{
		return And(tc,Op.EQUALS,v);
	}

	public WhereClause And(TableColumn tc,long v)
	{
		return And(tc,Op.EQUALS,v);
	}

	public WhereClause And(TableColumn tc,IDBase v)
	{
		return And(tc,Op.EQUALS,v);
	}
	
	public WhereClause And(TableColumn tc,double v)
	{
		return And(tc,Op.EQUALS,v);
	}

	public WhereClause And(TableColumn tc,Object v,DBType dbType)
	{
		return And(tc,Op.EQUALS,v,dbType);
	}
	
	public WhereClause And(TableColumn tc,DBInputColumnMap columnMap)
	{
		return And(tc,Op.EQUALS,columnMap);
	}

	public WhereClause And(TableColumn tc,Op op,SubqueryStatement subquery)
	{
		return Add
		(
			ConjunctiveOp.AND,
			new RelationalExpression(tc,op,subquery)
		);
	}
	
	public WhereClause And(TableColumn tc1,Op op,TableColumn tc2)
	{
		return Add
		(
			ConjunctiveOp.AND,
			new RelationalExpression(tc1,op,tc2)
		);
	}

	public WhereClause And(TableColumn tc,Op op)
	{
		return Add
		(
			ConjunctiveOp.AND,
			new RelationalExpression(tc,op)
		);
	}

	public WhereClause And(TableColumn tc,Op op,String v)
	{
		return Add
		(
			ConjunctiveOp.AND,
			new RelationalExpression(tc,op,v)
		);
	}
	
	public WhereClause And(TableColumn tc,Op op,short v)
	{
		return Add
		(
			ConjunctiveOp.AND,
			new RelationalExpression(tc,op,v)
		);
	}

	public WhereClause And(TableColumn tc,Op op,int v)
	{
		return Add
		(
			ConjunctiveOp.AND,
			new RelationalExpression(tc,op,v)
		);
	}

	public WhereClause And(TableColumn tc,Op op,long v)
	{
		return Add
		(
			ConjunctiveOp.AND,
			new RelationalExpression(tc,op,v)
		);
	}

	public WhereClause And(TableColumn tc,Op op,IDBase v)
	{
		return Add
		(
			ConjunctiveOp.AND,
			new RelationalExpression(tc,op,v)
		);
	}
	
	public WhereClause And(TableColumn tc,Op op,double v)
	{
		return Add
		(
			ConjunctiveOp.AND,
			new RelationalExpression(tc,op,v)
		);
	}

	public WhereClause And(TableColumn tc,Op op,Object v,DBType dbType)
	{
		return Add
		(
			ConjunctiveOp.AND,
			new RelationalExpression(tc,op,v,dbType)
		);
	}
	
	public WhereClause And(TableColumn tc,Op op,DBInputColumnMap inputColumnMap)
	{
		return Add
		(
			ConjunctiveOp.AND,
			new RelationalExpression(tc,op,inputColumnMap)
		);
	}
	
	public WhereClause And(RelationalExpression re)
	{
		return Add
		(
			ConjunctiveOp.AND,
			re
		);
	}

	public WhereClause Or(WhereClause where)
	{
		return Add(ConjunctiveOp.OR, where);
	}
	
	public WhereClause Or(TableColumn tc,SubqueryStatement subquery)
	{
		return Or(tc,Op.EQUALS,subquery);
	}
	
	public WhereClause Or(TableColumn tc1,TableColumn tc2)
	{
		return Or(tc1,Op.EQUALS,tc2);
	}

	public WhereClause Or(TableColumn tc,String v)
	{
		return Or(tc,Op.EQUALS,v);
	}
	
	public WhereClause Or(TableColumn tc,short v)
	{
		return Or(tc,Op.EQUALS,v);
	}

	public WhereClause Or(TableColumn tc,int v)
	{
		return Or(tc,Op.EQUALS,v);
	}

	public WhereClause Or(TableColumn tc,long v)
	{
		return Or(tc,Op.EQUALS,v);
	}

	public WhereClause Or(TableColumn tc,IDBase v)
	{
		return Or(tc,Op.EQUALS,v);
	}
	
	public WhereClause Or(TableColumn tc,double v)
	{
		return Or(tc,Op.EQUALS,v);
	}

	public WhereClause Or(TableColumn tc,Object v,DBType dbType)
	{
		return Or(tc,Op.EQUALS,v,dbType);
	}

	public WhereClause Or(TableColumn tc,DBInputColumnMap columnMap)
	{
		return Or(tc,Op.EQUALS,columnMap);
	}
	
	public WhereClause Or(TableColumn tc,Op op,SubqueryStatement subquery)
	{
		return Add
		(
			ConjunctiveOp.OR,
			new RelationalExpression(tc,op,subquery)
		);
	}
	
	public WhereClause Or(TableColumn tc1,Op op,TableColumn tc2)
	{
		return Add
		(
			ConjunctiveOp.OR,
			new RelationalExpression(tc1,op,tc2)
		);
	}

	public WhereClause Or(TableColumn tc,Op op)
	{
		return Add
		(
			ConjunctiveOp.OR,
			new RelationalExpression(tc,op)
		);
	}
	
	public WhereClause Or(TableColumn tc,Op op,String v)
	{
		return Add
		(
			ConjunctiveOp.OR,
			new RelationalExpression(tc,op,v)
		);
	}
	
	public WhereClause Or(TableColumn tc,Op op,short v)
	{
		return Add
		(
			ConjunctiveOp.OR,
			new RelationalExpression(tc,op,v)
		);
	}

	public WhereClause Or(TableColumn tc,Op op,int v)
	{
		return Add
		(
			ConjunctiveOp.OR,
			new RelationalExpression(tc,op,v)
		);
	}

	public WhereClause Or(TableColumn tc,Op op,long v)
	{
		return Add
		(
			ConjunctiveOp.OR,
			new RelationalExpression(tc,op,v)
		);
	}

	public WhereClause Or(TableColumn tc,Op op,IDBase v)
	{
		return Add
		(
			ConjunctiveOp.OR,
			new RelationalExpression(tc,op,v)
		);
	}
	
	public WhereClause Or(TableColumn tc,Op op,double v)
	{
		return Add
		(
			ConjunctiveOp.OR,
			new RelationalExpression(tc,op,v)
		);
	}
	
	public WhereClause Or(TableColumn tc,Op op,Object v,DBType dbType)
	{
		return Add
		(
			ConjunctiveOp.OR,
			new RelationalExpression(tc,op,v,dbType)
		);
	}

	public WhereClause Or(TableColumn tc,Op op,DBInputColumnMap columnMap)
	{
		return Add
		(
			ConjunctiveOp.OR,
			new RelationalExpression(tc,op,columnMap)
		);
	}
	
	public WhereClause Or(RelationalExpression re)
	{
		return Add
		(
			ConjunctiveOp.OR,
			re
		);
	}
	
	public PrintWriter Generate(PrintWriter out) 
		throws IOException
	{
		boolean firstIteration = true;
		
		for(WhereClauseElement wce:elements)
		{
			if(!firstIteration)
			{
				out.write(' ');
				conjunctive.Generate(out);
				out.write(' ');
			}
			
			if(wce.IsWhereClause())
			{
				out.write('(');
				wce.GetWhereClause().Generate(out);
				out.write(')');
			}
			else
			{
				wce.GetRelationalExpression().Generate(out);
			}
			
			firstIteration = false;
		}
		
		return out;
	}
	
	public void GetParameterValues(LinkedList<DBInputColumnMap> values)
	{
		for(WhereClauseElement wce:elements)
		{
			if(wce.IsWhereClause())
			{
				wce.GetWhereClause().GetParameterValues(values);
			}
			else
			{
				wce.GetRelationalExpression().GetParameterValues(values);
			}
		}
	}
	
	//Internals
	private WhereClause Add(ConjunctiveOp conjunctive,RelationalExpression re)
	{
		if(owner != null)
		{
			owner.Invalidate();
		}
		
		if(this.conjunctive == conjunctive || elements.size() < 2)
		{
			elements.add(new WhereClauseElement(re));
			this.conjunctive = conjunctive;
		}
		else
		{
			WhereClause temp = new WhereClause(this);
			
			this.conjunctive = conjunctive;
			
			elements.clear();		
			elements.add(new WhereClauseElement(temp));
			elements.add(new WhereClauseElement(re));
		}
		
		return this;
	}

	private WhereClause Add(ConjunctiveOp conjunctive,WhereClause where)
	{
		if(where == null || where.IsEmpty())
		{
			return this;
		}
		
		if(owner != null)
		{
			owner.Invalidate();
		}

		if(this.conjunctive == conjunctive || elements.size() < 2)
		{
			elements.add(new WhereClauseElement(where));
			this.conjunctive = conjunctive;
		}
		else
		{
			WhereClause temp = new WhereClause(this);
			
			this.conjunctive = conjunctive;
			
			elements.clear();		
			elements.add(new WhereClauseElement(temp));
			elements.add(new WhereClauseElement(where));
		}
		
		return this;
	}
	
	private ConjunctiveOp conjunctive;
	private LinkedList<WhereClauseElement> elements;
	private SQLStatement owner;
}
